package com.wkg.lotterysystem.service.mq;

import cn.hutool.core.date.DateUtil;
import com.wkg.lotterysystem.common.exception.ServiceException;
import com.wkg.lotterysystem.common.utils.JacksonUtils;
import com.wkg.lotterysystem.common.utils.MailUtil;
import com.wkg.lotterysystem.common.utils.SMSUtil;
import com.wkg.lotterysystem.controller.param.DrawPrizeParam;
import com.wkg.lotterysystem.dao.dataobject.ActivityPrizeDo;
import com.wkg.lotterysystem.dao.dataobject.WinningRecordDO;
import com.wkg.lotterysystem.dao.mapper.ActivityPrizeMapper;
import com.wkg.lotterysystem.dao.mapper.WinningRecordMapper;
import com.wkg.lotterysystem.service.DrawPrizeService;
import com.wkg.lotterysystem.service.activitystatus.ActivityStatusManager;
import com.wkg.lotterysystem.service.dto.ConvertActivityStatusDTO;
import com.wkg.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.wkg.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.wkg.lotterysystem.service.enums.ActivityStatusEnum;
import com.wkg.lotterysystem.service.enums.ActivityUserStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.wkg.lotterysystem.common.config.DirectRabbitConfig.QUEUE_NAME;

@Component
@RabbitListener(queues = QUEUE_NAME)
public class MqReceiver {

    private static final Logger log = LoggerFactory.getLogger(MqReceiver.class);
    @Autowired
    private DrawPrizeService drawPrizeService;
    @Autowired
    private ActivityStatusManager activityStatusManager;


    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Autowired
    private MailUtil mailUtil;
    @Autowired
    private SMSUtil smsUtil;
    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;
    @Autowired
    private WinningRecordMapper winningRecordMapper;

    @RabbitHandler
    public void process(Map<String, String> message) throws Exception {
        //发送消息为map类型，接收而要使用map
        log.info("MQ成功接收到消息,message:{}", JacksonUtils.writeValueAsString(message));
        String paramString = message.get("messageData");
        DrawPrizeParam param = JacksonUtils.readValue(paramString, DrawPrizeParam.class);
        //处理抽奖流程
        try {
            //校验抽奖请求是否有效
            if (!drawPrizeService.checkDrawPrizeParam(param)) {
                return;
            }

            //活动、奖品、人员状态处理（状态扭转处理）重要！！有设计模式
            statusConvert(param);

            //保存中奖者名单
            List<WinningRecordDO> winningRecordDOList = drawPrizeService.saveWinnerRecoeds(param);


            //通知中奖者（邮箱，短信）
            //抽奖之后的后续流程,异步(并发)处理
            asyncExecute(winningRecordDOList);

        } catch (ServiceException e) {
            log.error("处理MQ消息异常！{}:{}", e.getCode(), e.getMessage(), e);
            //需要保证事务一致性(回滚)
            rollback(param);

            //抛出异常: 消息重试(解决异常:代码bug,网络问题,服务问题
            throw e;
        } catch (Exception e) {
            log.error("处理MQ消息异常！", e);
            //需要保证事务一致性(回滚),抛出异常

            //抛出异常: 消息重试(解决异常:代码bug,网络问题,服务问题
            throw e;
        }
    }

    /**
     * 处理抽奖异常地回滚行为 :回复处理请求之前的库状态
     *
     * @param param
     */
    private void rollback(DrawPrizeParam param) {

        //1.回滚状态:获得,奖品,状态
        //判断状态是否需要回滚
        if (!statusNeedRollback(param)) {
            //不需要:return
            return;
        }

        //需要回滚:回滚
        rollbackStatus(param);

        //2.回滚中奖者名单
        //是否需要回滚
        if (!winnerNeedRollback(param)) {
            //不需要回滚:return
            return;
        }

        //需要:回滚
        rollbackWinner(param);
    }

    /**
     * 回滚中奖者记录:删除奖品下的中奖者
     *
     * @param param
     */
    private void rollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecord(param.getActivityId(), param.getPrizeId());
    }

    private boolean winnerNeedRollback(DrawPrizeParam param) {
        //判断活动中的奖品是否存在中奖者
        int count = winningRecordMapper.countByAPId(param.getActivityId(), param.getPrizeId());
        return count > 0;

    }

    /**
     * 回复相关状态
     *
     * @param param
     */
    private void rollbackStatus(DrawPrizeParam param) {
        // 设计状态的恢复,使用ActivityStatusManager
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserId(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.winner::getUserId)
                        .collect(Collectors.toList()));
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.INIT);
        activityStatusManager.rollbackhandlerEvent(convertActivityStatusDTO);

    }

    private boolean statusNeedRollback(DrawPrizeParam param) {
        //判断获得+奖品+人员相关状态是否已经扭转(正常思路)
        //扭转状态时,保证了食物一致性,要么都扭转,要不都没扭转(不包含活动):
        // 因此,只需要判断人眼/奖品是否扭转过,就能判断出状态是否全部扭转
        //不能判断活动是否已经扭转
        //结论:判断奖品状态是否扭转,就能判断出全部状态是否扭转
        ActivityPrizeDo activityPrizeDo = activityPrizeMapper.selectByAPId(param.getPrizeId(), param.getActivityId());

        //已经扭转,需要回滚
        return activityPrizeDo.getStatus().equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name());
    }

    /**
     * 并发处理抽奖后续流程
     *
     * @param winningRecordDOList
     */
    private void asyncExecute(List<WinningRecordDO> winningRecordDOList) {
        //通过线程池 ThreadPoolTaskExecutor
        //扩展: 加入策略模式或者其他设计模式来完成后续操作
        //短信通知
        threadPoolTaskExecutor.execute(() -> sendMessage(winningRecordDOList));

        //邮件通知
        threadPoolTaskExecutor.execute(() -> sendMail(winningRecordDOList));

    }

    /**
     * 发送邮件
     *
     * @param winningRecordDOList
     */
    private void sendMail(List<WinningRecordDO> winningRecordDOList) {
        if (CollectionUtils.isEmpty(winningRecordDOList)) {
            log.info("中奖列表为空,不用发邮件");
            return;
        }
        for (WinningRecordDO winningRecordDO : winningRecordDOList) {
            //Hi,xxx.恭喜你在抽奖活动中获得二等奖:吹风机,获奖时间为18:18:44,请尽快领取您的奖励
            String context = "Hi，" + winningRecordDO.getWinnerName() + "。恭喜你在"
                    + winningRecordDO.getActivityName() + "活动中获得"
                    + ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage()
                    + "：" + winningRecordDO.getPrizeName() + "。获奖时间为"
                    + DateUtil.formatTime(winningRecordDO.getWinningTime()) + "，请尽快领 取您的奖励！";
//                    + winningRecordDO.getWinningTime() + "，请尽快领 取您的奖励！";
            //发送邮件
            mailUtil.sendSampleMail(winningRecordDO.getWinnerEmail(), "中奖通知", context);
        }

    }

    /**
     * 发送短信
     *
     * @param winningRecordDOList
     */
    private void sendMessage(List<WinningRecordDO> winningRecordDOList) {
        if (CollectionUtils.isEmpty(winningRecordDOList)) {
            log.info("中奖列表为空,不用发短信");
            return;
        }

//        Hi，${name}。恭喜你在${activityName}活动中获得${prizeTiers}，奖品为${prizeName}。
//        获奖时间为${winningTime}，请尽快领取您的奖励！

        for (WinningRecordDO winningRecordDO : winningRecordDOList) {
            Map<String, String> map = new HashMap<>();
            map.put("name", winningRecordDO.getWinnerName());
            map.put("activityName", winningRecordDO.getActivityName());
            map.put("prizeTiers", winningRecordDO.getPrizeTier());
            map.put("prizeName", winningRecordDO.getPrizeName());
            map.put("winningTime", DateUtil.formatTime(winningRecordDO.getWinningTime()));
//            map.put("winningTime", winningRecordDO.getWinningTime());
            smsUtil.sendMessage("SMS_154950909",
                    winningRecordDO.getWinnerPhoneNumber().getValue(),
                    JacksonUtils.writeValueAsString(map));
        }

    }

    /**
     * 状态扭转
     *
     * @param param
     */
    private void statusConvert(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETED);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.COMPLETED);
        convertActivityStatusDTO.setUserId(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.COMPLETED);
        activityStatusManager.handlerEvent(convertActivityStatusDTO);
    }


    /**
     * 状态扭转
     *
     * @param param
     *//*
    private void statusConvert(DrawPrizeParam param) {
        //问题：
        // 1、活动状态扭转有依赖性，导致代码维护性差
        // 2.状态扭转体条件可能会扩展，当前写法，扩展性差，维护性差
        // 3.代码的灵活性，扩展性，维护性极差
        // 解决方案：设计模式（责任链模式，策略模式）

        //活动:RUNNING-->COMPLETED  (全部奖品抽完之后才改变状态)
        //奖品:INIT-->COMPLETED
        //人员:INIT-->COMPLETED

        //1、扭转奖品状态
        // 查询活动关联的奖品信息
        // 条件判断是否符合扭转奖品状态： 判断当前状态是否为COMPLETED，如果不是，要扭转
        // 进行扭转

        //2、扭转人员状态
        // 查询活动关联的人员信息
        // 条件判断是否符合扭转人员状态：判断当前状态是否为COMPLETED，如果不是，要扭转
        // 进行扭转

        //3、扭转活动状态（必须在扭转奖品状态之后完成）
        // 查询活动信息
        // 条件判断是否符合扭转活动状态：判断当前状态是否为COMPLETED，如果不是，且全部奖品抽完之后才改变状态
        // 进行扭转

        //4、更新活动完整信息缓存
    }*/
}



















